/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package pl.gliwice.iitis.spatialnn.network;

import pl.gliwice.iitis.spatialnn.network.construct.ConstructionParams;
import pl.gliwice.iitis.spatialnn.network.construct.Gaussian;

/**
 * A neuron type.
 * 
 * @author Artur Rataj
 */
public enum NeuronType {
        EMPTY,
        CONNECT,
        ADD,
        MEAN,
        MULT,
        NEG,
        INV,
        ABS,
        MINV,
        MAXV,
        MINP,
        MAXP,;

    final static double ALPHA = 0.04;
    final static double ZETA = 0.005*1+0.5*0;
    public static double CUSTOM_INPUT_SPREAD = Math.PI/4.0;
    /**
     * Returns the name, as defined in the I/O format "neuron type" string.
     * 
     * @return type name
     */
    public String getIOName() {
        switch(this) {
            case EMPTY:
                return "empty";
                
            case CONNECT:
                return "connect";
                
            case ADD:
                return "add";
                
            case MEAN:
                return "mean";
                
            case MULT:
                return "mult";
                
            case NEG:
                return "neg";
                
            case INV:
                return "inv";
                
            case ABS:
                return "abs";
                
            case MINV:
                return "minv";
                
            case MAXV:
                return "maxv";
                
            case MINP:
                return "minp";
                
            case MAXP:
                return "maxp";
                
            default:
                throw new RuntimeException("undefined type");
        }
    }
    /**
     * Converts what <code>getIOName()</code> returns into <code>NeuronType</code>.
     * 
     * @return neuron type, null if the name does not match
     */
    public static NeuronType fromIOName(String name) {
        for(NeuronType t : NeuronType.values())
            if(t.getIOName().equals(name))
                return t;
        return null;
    }
    /**
     * Returns the minimum number of inputs for a given type.
     * 
     * @return minimum number of inputs
     */
    public int getMinInNum() {
        switch(this) {
            case EMPTY:
                return 0;
                
            case CONNECT:
                return 1;
                
            case ADD:
                return 1;
                
            case MEAN:
                return 1;
                
            case MULT:
                return 1;
                
            case NEG:
                return 1;
                
            case INV:
                return 1;
                
            case ABS:
                return 2;
                
            case MINV:
                return 1;
                
            case MAXV:
                return 1;
                
            case MINP:
                return 1;
                
            case MAXP:
                return 1;
                
            default:
                throw new RuntimeException("undefined type");
        }
    }
    /**
     * Returns the maximum number of inputs for a given type.
     * 
     * @return minimum number of inputs, -1 for a lack of a maximum value
     */
    public int getMaxInNum() {
        switch(this) {
            case EMPTY:
                return 0;
                
            case CONNECT:
                return 1;
                
            case ADD:
                return -1;
                
            case MEAN:
                return -1;
                
            case MULT:
                return -1;
                
            case NEG:
                return 1;
                
            case INV:
                return 1;
                
            case ABS:
                return -1;
                
            case MINV:
                return -1;
                
            case MAXV:
                return -1;
                
            case MINP:
                return -1;
                
            case MAXP:
                return -1;
                
            default:
                throw new RuntimeException("undefined type");
        }
    }
    /**
     * Returns the default number of inputs for a given type.
     * 
     * @return number of inputs, -1 for a lack of a default value
     */
    public int getDefaultInNum() {
        switch(this) {
            case EMPTY:
                return 0;
                
            case CONNECT:
                return 1;
                
            case ADD:
                return 2;
                
            case MEAN:
                return 2;
                
            case MULT:
                return 2;
                
            case NEG:
                return 1;
                
            case INV:
                return 1;
                
            case ABS:
                return 2;
                
            case MINV:
                return -1;
                
            case MAXV:
                return -1;
                
            case MINP:
                return -1;
                
            case MAXP:
                return -1;
                
            default:
                throw new RuntimeException("undefined type");
        }
    }
    /**
     * Returns if the type can have an output.
     * 
     * @return if an output is possible
     */
    public boolean getOutPossible() {
        switch(this) {
            case EMPTY:
                return false;
                
            case CONNECT:
            case ADD:
            case MEAN:
            case MULT:
            case NEG:
            case INV:
            case ABS:
            case MINV:
            case MAXV:
            case MINP:
            case MAXP:
                return true;
                
            default:
                throw new RuntimeException("undefined type");
        }
    }
    /**
     * Returns `scan width' construction params, typical for neurons like
     * <code>ABS</code> or <code>ADD</code>, when not inputs after a
     * turn.
     * 
     * @param inputsNum defines distribution of the number of inputs
     * @param negativeInAngleSigma if <code>ConstructionParams</code>'s
     * <code>inAngleSigma</code> should be negated; see that field's docs
     * for details
     * @return construction parameters
     */
    public static ConstructionParams scanWidthCP(Gaussian inputsNum,
            boolean negativeInAngleSigma) {
        double sigma = ALPHA;
        return new ConstructionParams(inputsNum,
                        new Gaussian(0.02, 0.001),
                negativeInAngleSigma ? -sigma : sigma, ZETA);
    }
    /**
     * Returns `scan height' construction params, typical for min/max neurons and
     * for turn inputs.
     * 
     * @param inputsNum defines distribution of the number of inputs
     * @param extend extend the scanning region approxinmately that many times
     * along the growth axis, in comparison to the default shape
     * @return construction parameters
     */
    public static ConstructionParams scanHeightCP(Gaussian inputsNum, double extend) {
        return new ConstructionParams(inputsNum,
                        new Gaussian(0.18*extend, 0.06*extend),
                0.04/1/extend, ZETA);
    }
    /**
     * Returns the construction attributes for a given type.
     * 
     * @param turnInputs if the input stats should represent turn inputs
     * @return construction parameters
     */
    public ConstructionParams getConstructionParams(boolean turnInputs) {
        final double EXTEND = 1.0;
        
        switch(this) {
            case EMPTY:
                throw new RuntimeException("unimplemented");
                
            case CONNECT:
                throw new RuntimeException("unimplemented");
                
            case ADD:
            case MEAN:
            case MULT:
            case ABS:
            {
                if(turnInputs)
                    return scanHeightCP(new Gaussian(20.0, 2.0), EXTEND);
                else
                    return scanWidthCP(new Gaussian(2.0, 0.0), true);
            }   
            case NEG:
            case INV:
            {
                Gaussian inputs = new Gaussian(1.0, 0.0);
                if(turnInputs)
                    return scanHeightCP(inputs, EXTEND);
                else
                    return scanWidthCP(inputs, false);
            }  
            case MINV:
            case MAXV:
            case MINP:
            case MAXP:
            {
                return scanHeightCP(new Gaussian(20.0, 2.0), EXTEND);
            }
                
            default:
                throw new RuntimeException("undefined type");
        }
    }
    /**
     * Returns a single character, that symbolises this type.
     * 
     * @return a character, representing this type
     */
    public char getSymbolChar() {
        char s;
        switch(this) {
            case ABS:
                s = '=';
                break;
                
            case ADD:
                s = '+';
                break;
            
            case MEAN:
                s = '~';
                break;

            case CONNECT:
                s = 'C';
                break;
                
            case EMPTY:
                s = ' ';
                break;
                
            case INV:
                s = '/';
                break;
                
            case MAXV:
                s = 'M';
                break;
                
            case MINV:
                s = 'm';
                break;

            case MAXP:
                s = '>';
                break;
                
            case MINP:
                s = '<';
                break;
                
            case MULT:
                s = '*';
                break;
                
            case NEG:
                s = '-';
                break;
                
            default:
                throw new RuntimeException("unknown  type");
        }
        return s;
    }
    /**
     * Returns a mean value.
     * 
     * @param in array with input values
     * @param num number of input values in <code>in<code>
     * @return a mean value
     */
    public double getMean(double[] in, int num) {
        double sum = 0.0;
        for(int i = 0; i < num; ++i)
            sum += in[i];
        return sum/num;
    }
    /**
     * Returns an output value of a neuron of this type, given input
     * values.
     * 
     * @param in array with input values
     * @param num number of input values in <code>in<code>
     * @param loc relative locations of inputs of a neurons, to be chosen
     * if the type is <code>MINP</code> or <code>MAXP</code>;
     * for other types, this parameter can be null
     * @return an output value
     */
    public double get(double[] in, int num, double[] loc) {
        switch(this) {
            case ABS:
            {
                double mean = getMean(in, num);
                double diff = 0.0;
                for(int i = 0; i < num; ++i)
                    diff += Math.abs(in[i] - mean);
                //diff = Math.min(diff, 0.002);
                return diff;
            }
            case ADD:
            {
                double sum = 0.0;
                for(int i = 0; i < num; ++i)
                    sum += in[i];
                return sum;
            }
            case MEAN:
            {
                return getMean(in, num);
            }
            case CONNECT:
            {
                if(num != 1)
                    throw new RuntimeException("number of inputs not supported");
                return in[0];
            }
            case EMPTY:
            {
                throw new RuntimeException("not supported");
            }  
            case INV:
            {
                if(num != 1)
                    throw new RuntimeException("number of inputs not supported");
                return 1.0/in[0];
            }
            case MAXV:
            {
                double max = -Double.MAX_VALUE;
                for(int i = 0; i < num; ++i)
                    if(max < in[i])
                        max = in[i];
                return max;
            }  
            case MINV:
            {
                double min = Double.MAX_VALUE;
                for(int i = 0; i < num; ++i)
                    if(min > in[i])
                        min = in[i];
                return min;
            }
            case MAXP:
            {
                double max = -Double.MAX_VALUE;
                int maxIndex = -1;
                for(int i = 0; i < num; ++i)
                    if(max < in[i]) {
                        max = in[i];
                        maxIndex = i;
                    }
                return loc[maxIndex];
            }  
            case MINP:
            {
                double min = Double.MAX_VALUE;
                int minIndex = -1;
                for(int i = 0; i < num; ++i)
                    if(min > in[i]) {
                        min = in[i];
                        minIndex = i;
                    }
                return loc[minIndex];
            }  
            case MULT:
            {
                double prod = 1.0;
                for(int i = 0; i < num; ++i)
                    prod *= in[i];
                return prod;
            }
            case NEG:
            {
                if(num != 1)
                    throw new RuntimeException("number of inputs not supported");
                return -in[0];
            }  
            default:
                throw new RuntimeException("unknown  type");
        }
    }
}
